//
//  HelperAppDelegate.m
//  UnityCapture
//
//  Created by Adrian on 27.10.09.
//  Copyright 2009 __MyCompanyName__. All rights reserved.
//

#import "HelperAppDelegate.h"

#import <Quartz/Quartz.h>

#define kSettingsHiddenSize 102
#define kSettingsVisibleSize 458

#define kResolutionPopUpFullWidth 201
#define kResolutionPopUpCustomWidth 107

@interface HelperAppDelegate (Private)
- (NSString*)finalOutputPath;
- (void)updateOutputPath;
- (BOOL)chooseNewOutputPath;
- (void)updateResolution;
- (void)updateSettingsFromComponent;
- (void)setSettingsVisible:(BOOL)visible animate:(BOOL)animate;
- (BOOL)checkOutputPath;
- (void)setWindowOnTop:(BOOL)ontop;
- (void)setOverwriteStrategy:(UCOverwriteStrategy)strategy;
@end

@implementation HelperAppDelegate (Private)

- (NSString*)finalOutputPath
{
	if (pathNumbering > 0) {
		
		NSString *basePath = [outputPath stringByDeletingPathExtension];
		NSString *extension = [outputPath pathExtension];
		return [NSString stringWithFormat:@"%@ %u.%@", basePath, pathNumbering, extension];
	
	} else
		return outputPath;
}

- (void)updateOutputPath
{
	
	NSString *finalPath = [self finalOutputPath];
	
	if ([finalPath length] < 30) {
		[pathField setStringValue:finalPath];
		[pathField setToolTip:nil];
	} else {
		[pathField setStringValue:
			[@"... " stringByAppendingString:[finalPath substringFromIndex:[finalPath length]-25]]];
		[pathField setToolTip:finalPath];
	}
}

- (BOOL)chooseNewOutputPath
{
	NSSavePanel *savePanel = [NSSavePanel savePanel];
	[savePanel setRequiredFileType:@"mov"];
	[savePanel setCanCreateDirectories:YES];
	[savePanel setCanSelectHiddenExtension:YES];
	
	[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
	if ([savePanel runModalForDirectory:[outputPath stringByDeletingLastPathComponent] 
								   file:[outputPath lastPathComponent]] 
		== NSOKButton) {
		
		[outputPath release];
		outputPath = [[savePanel filename] retain];
		[self updateOutputPath];
		return YES;
	}
	return NO;
}

- (void)updateResolution
{
	NSInteger selection = [resolutionPopupButton selectedTag];
	
	switch (selection) {
		case 0:
			captureResolution = NSMakeSize([widthTextField floatValue], [heightTextField floatValue]);
			break;
		case 144:
			captureResolution = NSMakeSize(176, 144);
			break;
		case 240:
			captureResolution = NSMakeSize(320, 240);
			break;
		case 288:
			captureResolution = NSMakeSize(352, 288);
			break;
		case 480:
			captureResolution = NSMakeSize(640, 480);
			break;
		case 576:
			captureResolution = NSMakeSize(768, 576);
			break;
		case 720:
			captureResolution = NSMakeSize(1280, 720);
			break;
		case 1080:
			captureResolution = NSMakeSize(1920, 1080);
			break;
		default:
			captureResolution = NSMakeSize(-1, -1);
			break;
	}
}

- (void)updateSettingsFromComponent
{
	SCTemporalSettings temporalSettings;
	SCSpatialSettings spatialSettings;
	
	SCGetInfo(component, scSpatialSettingsType, &spatialSettings);
	codecType = spatialSettings.codecType;
	
	SCGetInfo(component, scTemporalSettingsType, &temporalSettings);
	framesPerSecond = Fix2X(temporalSettings.frameRate);
	
	OSErr theError;
	CodecInfo codecInfo;
	
	theError = GetCodecInfo(&codecInfo, codecType, 0);
	[codecField setStringValue:(NSString*)CFStringCreateWithPascalString(
											nil, codecInfo.typeName, CFStringGetSystemEncoding())];
	
	NSString *format = @"%.f";
	if (round(framesPerSecond) != framesPerSecond)
		format = @"%.2f";
	[fpsField setStringValue:[NSString stringWithFormat:format,framesPerSecond]];
}

- (void)setSettingsVisible:(BOOL)visible animate:(BOOL)animate
{
	settingsVisible = visible;
	
	NSRect frame = [window frame];
	float heightBefore = frame.size.height;
	
	if (settingsVisible)
		frame.size = NSMakeSize(350, kSettingsVisibleSize);
	else
		frame.size = NSMakeSize(350, kSettingsHiddenSize);
	
	frame = [window frameRectForContentRect:frame];
	frame.origin.y -= (frame.size.height - heightBefore);
	
	if (animate)
		[[window animator] setFrame:frame display:YES animate:YES];
	else
		[window setFrame:frame display:YES animate:NO];
	
	[disclosure setState:settingsVisible];
}

- (BOOL)checkOutputPath
{
	if ([[NSFileManager defaultManager] fileExistsAtPath:[self finalOutputPath]]) {
		
		// Overwrite existing file
		if (overwriteStrategy == UCExistingOverwrite)
			return YES;
		
		// Find sequentually numbered unused name
		else if (overwriteStrategy == UCExistingNumberSequentially) {
			
			pathNumbering = 0;
			NSString *newPath;
			
			do {
				pathNumbering++;
				newPath = [self finalOutputPath];
			} while ([[NSFileManager defaultManager] fileExistsAtPath:newPath]);
		
		// Open choose dialog
		} else if (overwriteStrategy == UCExistingChooseNew) {
			
			return [self chooseNewOutputPath];
		
		// Display an error	
		} else if (overwriteStrategy == UCExistingError) {
			
			NSInteger reply = NSRunAlertPanel(
				@"Output file already exists.",
				[NSString stringWithFormat:@"\"%@\" already exists. Do you want to replace it?",
					[outputPath lastPathComponent]],
				@"Abort",
				@"Replace",
				nil
			);
			
			if (reply == NSAlertDefaultReturn)
				return NO;
		}
	}
	[self updateOutputPath];
	return YES;
}

- (void)setWindowOnTop:(BOOL)ontop
{
	windowOnTop = ontop;
	[windowOnTopButton setState:windowOnTop];
	if (windowOnTop)
		[window setLevel:NSFloatingWindowLevel];
	else
		[window setLevel:NSNormalWindowLevel];
}

- (void)setOverwriteStrategy:(UCOverwriteStrategy)strategy
{
	overwriteStrategy = strategy;
	[overwriteModeButton selectItemAtIndex:overwriteStrategy];
}

@end



@implementation HelperAppDelegate

// INTERFACE ELEMENTS
@synthesize window;

@synthesize disclosure;
@synthesize pathChangeButton;
@synthesize pathShowButton;
@synthesize codecChangeButton;
@synthesize startStopButton;

@synthesize windowOnTopButton;

@synthesize overwriteModeButton;

@synthesize pathField;
@synthesize codecField;
@synthesize fpsField;

@synthesize statFpsField;
@synthesize statTimeField;

@synthesize recordIcon;

@synthesize resolutionPopupButton;
@synthesize playerResolutionItem;
@synthesize customResolutionView;
@synthesize widthTextField;
@synthesize heightTextField;

- (void)awakeFromNib
{
	
	NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
	
	// Intialize output path
	if ([defaults objectForKey:@"MovieOutputPath"])
		outputPath = [[defaults stringForKey:@"MovieOutputPath"] retain];
	else
		outputPath = [[@"~/Desktop/Unity Capture.mov" stringByExpandingTildeInPath] retain];
	
	[self updateOutputPath];
	
	// Overwrite strategy
	if ([defaults objectForKey:@"OverwriteStrategy"])
		[self setOverwriteStrategy:[defaults integerForKey:@"OverwriteStrategy"]];
	else
		[self setOverwriteStrategy:UCExistingOverwrite];
	
	// Initialize compression settings
	long flags = scAllowEncodingWithCompressionSession;
	ComponentResult theError;
	QTAtomContainer container;
	
	component = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType);
	if(component == NULL) {
		NSLog(@"Compression component opening failed");
		return;
	}
	SCSetInfo(component, scPreferenceFlagsType, &flags);
	
	// Initialize resolution
	if ([defaults objectForKey:@"Resolution"])
		[resolutionPopupButton selectItemWithTag:[defaults integerForKey:@"Resolution"]];
	else
		[resolutionPopupButton selectItemWithTag:-1];
	
	[self resolutionPresetChanged:self];
	
	NSSize customResolution;
	if ([defaults objectForKey:@"CustomResolution"])
		customResolution = NSSizeFromString([defaults stringForKey:@"CustomResolution"]);
	else
		customResolution = NSMakeSize(800, 600);
	
	[widthTextField setIntValue:(int)customResolution.width];
	[heightTextField setIntValue:(int)customResolution.height];
	
	[self updateResolution];
	
	// Restore compression settings from user defaults
	NSData *data = [defaults objectForKey:@"CompressionDialogSettings"];
	if(data) {
		container = NewHandle([data length]);
		if(container) {
			[data getBytes:*container];
			theError = SCSetSettingsFromAtomContainer(component, container);
			if(theError)
				NSLog(@"SCSetSettingsFromAtomContainer() failed with error %i", theError);
			QTDisposeAtomContainer(container);
		}
		
	// Apply new defaults
	} else {
		
		SCTemporalSettings temporalSettings;
		SCSpatialSettings spatialSettings;
		
		SCGetInfo(component, scSpatialSettingsType, &spatialSettings);
		spatialSettings.codecType = kJPEGCodecType;
		theError = SCSetInfo(component, scSpatialSettingsType, &spatialSettings);
		
		if (theError) {
			NSLog(@"Failed to set default codec type.");
			return;
		}
		
		SCGetInfo(component, scTemporalSettingsType, &temporalSettings);
		temporalSettings.frameRate = 1964113; // 29.97 fps
		theError = SCSetInfo(component, scTemporalSettingsType, &temporalSettings);
		
		if (theError) {
			NSLog(@"Failed to set default frame rate.");
			return;
		}
	}
	
	[self updateSettingsFromComponent];
	
	// update window level
	if ([defaults objectForKey:@"KeepWindowOnTop"])
		[self setWindowOnTop:[defaults boolForKey:@"KeepWindowOnTop"]];
	else
		[self setWindowOnTop:YES];
	
	// settings visible
	if ([defaults objectForKey:@"SettingsVisible"])
		[self setSettingsVisible:[defaults boolForKey:@"SettingsVisible"] animate:NO];
	else
		[self setSettingsVisible:NO animate:NO];
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
	
	// Open client connection
	clientConnection = nil;
	while (!clientConnection) {
		clientConnection = [[NSConnection connectionWithRegisteredName:@"UnityCaptureCore" host:nil] retain];
		
		if (clientConnection)
			break;
		
		NSInteger reply = NSRunAlertPanel(
			@"Connection Failed",
			@"Connection to Unity Capture Core couldn't be established.\nMake sure the Unity product is running and try again.",
			@"Try again...",
			@"Quit",
			nil
		);
		
		if (reply == NSAlertAlternateReturn) {
			[[NSApplication sharedApplication] terminate:self];
			return;
		}
	}
	[clientConnection retain];
	coreProxy = [[clientConnection rootProxy] retain];
	[coreProxy setProtocolForProxy:@protocol(UCCoreProtocol)];
	coreProto = (id <UCCoreProtocol>)coreProxy;
	
	// Quit helper if connection is lost
	[[NSNotificationCenter defaultCenter] addObserver:NSApp
											 selector:@selector(terminate:)
												 name:NSConnectionDidDieNotification
											   object:clientConnection];
	
	// Open server connection
	serverConnection = [[NSConnection serviceConnectionWithName:@"UnityCaptureHelper" rootObject:self] retain];
	
	// Signal core that server has started
	[coreProto helperServerHasStarted];
}

- (IBAction)toggleSettings:(NSButton*)sender
{
	[self setSettingsVisible:([sender state] == NSOnState) animate:YES];
}

- (IBAction)changeOutputPath:(id)sender
{
	[self chooseNewOutputPath];
}

- (IBAction)showOutputPath:(id)sender
{
	NSWorkspace *ws = [NSWorkspace sharedWorkspace];
	
	NSString *finalPath = [self finalOutputPath];
	
	if ([[NSFileManager defaultManager] fileExistsAtPath:finalPath]) {
		[ws selectFile:finalPath
				inFileViewerRootedAtPath:[finalPath stringByDeletingLastPathComponent]];
	} else {
		[ws openFile:[finalPath stringByDeletingLastPathComponent]];
	}
}

- (IBAction)changeOverwriteStrategy:(NSPopUpButton*)sender
{
	[self setOverwriteStrategy:[sender indexOfSelectedItem]];
}

- (IBAction)changeCompressionSettings:(id)sender
{
	ComponentResult theError;
	
	// Display compression dialog to user
	[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
	theError = SCRequestSequenceSettings(component);
	if(theError) {
		if(theError != 1)
			NSLog(@"SCRequestSequenceSettings() failed with error %i", theError);
		return;
	}
	
	[self updateSettingsFromComponent];
}

- (IBAction)toggleWindowOnTop:(NSButton*)sender
{
	[self setWindowOnTop:([sender state] == NSOnState)];
}

- (IBAction)toggleCapture:(NSButton*)sender
{
	[self toggleCapture];
}

- (IBAction)resolutionPresetChanged:(id)sender
{
	BOOL customResolution = ([resolutionPopupButton selectedTag] == 0);
	
	[customResolutionView setHidden:(!customResolution)];
	
	if (customResolution)
		[resolutionPopupButton setFrameSize:NSMakeSize(kResolutionPopUpCustomWidth, 22)];
	else
		[resolutionPopupButton setFrameSize:NSMakeSize(kResolutionPopUpFullWidth, 22)];
}

- (void)toggleCapture
{
	if (!captureRunning) {
		[self startCapture];
	} else {
		[self stopCapture];
	}
}

- (void)startCapture
{
	if (![self checkOutputPath])
		return;
	
	[self updateResolution];
	
	captureRunning = YES;
	
	[startStopButton setTitle:@"Stop Capture"];
	
	[NSAnimationContext beginGrouping];
	[[NSAnimationContext currentContext] setDuration:1.5];
	[[recordIcon animator] setAlphaValue:1.0];
	[NSAnimationContext endGrouping];
	
	CABasicAnimation *anim;
	anim = [CABasicAnimation animationWithKeyPath:@"opacity"];
	anim.timeOffset = 1.5;
	anim.duration = 1.5;
	anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
	anim.repeatCount = INFINITY;
	anim.autoreverses = YES;
	anim.fromValue = [NSNumber numberWithFloat:1.0];
	anim.toValue = [NSNumber numberWithFloat:0.5];
	[[recordIcon layer] addAnimation:anim forKey:@"animateOpacity"];
	
	[coreProto startCapture];
}

- (void)stopCapture
{
	captureRunning = NO;
	
	[startStopButton setTitle:@"Start Capture"];
	
	[[recordIcon layer] removeAnimationForKey:@"animateOpacity"];
	
	[NSAnimationContext beginGrouping];
	[[NSAnimationContext currentContext] setDuration:0.5];
	[[recordIcon animator] setAlphaValue:0.0];
	[NSAnimationContext endGrouping];
	
	[statFpsField setStringValue:@"-"];
	[statTimeField setStringValue:@"-"];
	
	[coreProto stopCapture];
}

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
	if ([clientConnection isValid]) {
		[window orderOut:self];
		return NSTerminateCancel;
	} else
		return NSTerminateNow;
}

- (void)applicationWillTerminate:(NSNotification *)notification
{
	NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
	
	// Save path in defaults
	[defaults setObject:outputPath forKey:@"MovieOutputPath"];
	
	// Save overwrite strategy
	[defaults setInteger:overwriteStrategy forKey:@"OverwriteStrategy"];
	
	// Save resolution
	[defaults setInteger:[resolutionPopupButton selectedTag] forKey:@"Resolution"];
	[defaults setObject:NSStringFromSize(NSMakeSize([widthTextField floatValue], 
													[heightTextField floatValue])) 
				 forKey:@"CustomResolution"];
	
	// Save compression settings in user defaults
	QTAtomContainer container;
	ComponentResult theError;
	
	theError = SCGetSettingsAsAtomContainer(component, &container);
	if(theError)
		NSLog(@"SCSetSettingsFromAtomContainer() failed with error %i", theError);
	else {
		NSData *data = [NSData dataWithBytes:*container length:GetHandleSize(container)];
		[[NSUserDefaults standardUserDefaults] setObject:data forKey:@"CompressionDialogSettings"];
		QTDisposeAtomContainer(container);
	}
	
	// save options
	[defaults setBool:windowOnTop forKey:@"KeepWindowOnTop"];
	[defaults setBool:settingsVisible forKey:@"SettingsVisible"];
}

- (void)openCaptureWindow 
{
	[window makeKeyAndOrderFront:self];
}

- (NSString*)outputPath
{
	return [self finalOutputPath];
}

- (NSData*)compressionOptions
{
	QTAtomContainer container;
	ComponentResult theError;
	NSData *data;
	
	theError = SCGetSettingsAsAtomContainer(component, &container);
	if(theError)
		NSLog(@"SCSetSettingsFromAtomContainer() failed with error %i", theError);
	else {
		data = [NSData dataWithBytes:*container length:GetHandleSize(container)];
		QTDisposeAtomContainer(container);
	}
	
	return data;
}

- (NSValue*)captureResolution
{
	return [NSValue valueWithSize:captureResolution];
}

- (void)newPlayerResolutionWithWidth:(float)width andHeight:(float)height
{
	[playerResolutionItem setTitle:[NSString stringWithFormat:@"Player resolution (%.f x %.f)",
									width,height]];
}

- (void)updateStatsWithFps:(float)fps andTime:(float)time
{
	NSString *format = @"%.f";
	if (round(fps) != fps)
		format = @"%.2f";
	
	[statFpsField setStringValue:[NSString stringWithFormat:format, fps]];
	[statTimeField setStringValue:[NSString stringWithFormat:@"%.f seconds", time]];
}

- (void)dealloc
{
	CloseComponent(component);
	[outputPath release];
	
	[clientConnection release];
	[coreProxy release];
	[serverConnection release];
	
	[super dealloc];
}

@end
